- Part 1: Introduction
- Part 2: Sensor Reconnaissance → You are Here
- Part 3: Diverting EDR Telemetry to Private Infrastructure
- Part 4: Blending In — Upcoming
- Part 5: Abusing Blind Spots — Upcoming
- Part 6: Tampering Sensors — Upcoming
- Part 7: Repurposing Response — Upcoming
In this post, I'll go over a mix of topics that are useful for understanding EDR sensors and their configurations. The details of many of these techniques tend to be product-specific, and it's somewhat limiting to publicly discuss without case studies to show concrete examples. I hope this post will provide some pointers that you can extrapolate to your own R&D process.
When you land on a new target as an operator, one of the first things you might look for is whether AV/EDR software is running. You can list processes and see if any of them belong to known products, though several of them now offer the option to obfuscate the process name.
This is relatively simple to get around because sensors leave more of a footprint than just their process names. Other examples could include service names, constants for named pipes or trace sessions, and file/registry artifacts. This does require some more analysis but can also be prone to obfuscation should vendors have the inclination.
One part of a product's footprint that requires minimal analysis (and is less prone to obfuscation) is the digital signature of any kernel-mode drivers it loads. A discovery module can be written to enumerate loaded driver modules, and validate their signatures against signer names of known products. Here's an example of what the output would look like on a system with the FireEye Endpoint Agent running, if implemented as a Cobalt Strike Beacon Object File (BOF):
beacon> inline-execute /home/ghost/get_loaded_security_drivers.o [*] Tasked beacon to inline-execute /home/ghost/get_loaded_security_drivers.o [+] host called home, sent: 1907 bytes [+] received output: \systemroot\system32\drivers\fekern.sys -> FireEye, Inc.
Click here to view the framework-agnostic code on GitHub.
Sensor configurations can be used to store information about the servers they reach, enabled or disabled capabilities, verbosity levels for troubleshooting, and more. Usually they can be found in the registry or in a file format such as textual (e.g. INI, conf file) or binary (e.g. SQLite, proprietary). Some sensors store this data encrypted or in an obfuscated form. You can consider the options below if you're not sure where a particular configuration is:
- Product manuals or troubleshooting documentation, if available, can be valuable in directly knowing where and how the configuration is stored. Sometimes useful information can be found from second/third-parties or in forum posts.
- Some products also have diagnostic tools that can be used to assess the posture of the sensor. This process could involve parsing and exporting configuration data.
- A combination of static and dynamic analysis of the sensor can also help (e.g. identifying when the configuration is loaded into memory and how it's being parsed).
When you land on a target, you should look to see not only whether a sensor is running, but also how it is configured. You should be able to find out whether it collects as much as it is able to, or whether limitations have been placed to reduce noise. You can also check to see if there are any exclusion paths where you can safely run payloads from. For sensors with on-board detection engines, you can check to see whether they're enabled, and if so, what confidence level it's set to.
Turning Up Verbosity
Sensors tend to have options for more verbose logging to aid with troubleshooting. These settings are typically found in the Registry on Windows systems, and in configuration files for other operating systems. Some documented examples are below:
- Carbon Black Response
- CrowdStrike Falcon
03 00 00 00
- Tanium Client
Depending on the product, there can be more options beyond what's listed in documentation and some may facilitate logging through ETW. Further analysis of the binaries can be useful in fleshing those out.
Some of the gems I've been able to find in logs include: class names (to better understand code organization), applied policies, HTTP request contents, individual events logged in human readable form, and occasional error messages that identify blind spots in the sensor.
Debug strings, when available, continue to be useful in quickly understanding how various aspects of the sensors are structured and how they function. They tend to remain in binaries for some products presumably because of their utility for troubleshooting purposes.
In the fictional example below, the product has many calls to a logger function with this prototype:
// prototype void logger(int log_level, char* function_name, char* file_name, char* format_str, ...); // example call logger(3, "EventLogger::VerifyConfigurationData", "EventLogger.cpp", "Config data cannot be found (User[%ls], System[%ls])", L"HKEY_CURRENT_USER\\Software\\FancyEDR", L"HKEY_LOCAL_MACHINE\\Software\\FancyEDR");
The second argument is particularly helpful because, in the absence of debug symbols, it can be used to recover the name of the function/class that is calling it. So, it's possible to write a Python script for Ghidra that could automate this and get a better idea of the class structures as a result.
This is awesome because it can speed up the reversing process by allowing us to reason about the code at a higher level of abstraction. Click here to view the Ghidra script on GitHub.
If debug strings or symbols are not available in the latest version of a given sensor, you might get lucky and find them in older versions or implementations for other operating systems.
Most products include a kernel driver component in their sensors which is used to collect telemetry from the OS. It can also be used for other purposes such as decision making and sending telemetry, but it depends on the product. On Windows, drivers can register several callback functions which can be used to collect telemetry. @dotslashroot and @sbousseaden have published material on how they are used in Sysmon.
Below, you can see a high-level view for telemetry flow on some Windows-based sensors. In this flow, if a user launches
calc, this will trigger a callback for
PsSetCreateProcessNotifyRoutine. The driver may produce an event from the data it received in the callback, and the user-mode service will receive it from the driver so it can send it to the product server.
You should take the time to identify the interprocess communication (IPC) mechanisms that are exposed by the service and driver components. That said, there isn't much to add here that applies exclusively to AV/EDR products, and IPC mechanisms within this space can vary widely across products and operating systems. On Windows, some examples include custom service control codes, COM objects, and IOCTL dispatch functions.
There is valuable data you can glean from the sensor reconaissance process, whether it's to influence further research or to assess OPSEC on targets. When on a target system, detecting the product used and parsing its configuration can provide insights to influence your next steps. On the research side, logs and strings continue to provide a good starting point for avenues to investigate. Inferring the sensor's architecture and IPC mechanisms will give you a more solid foundation when applying the techniques discussed in subsequent posts.
The next post will discuss how to divert EDR telemetry to private infrastructure, so you can see what the sensor sees when developing bypasses (without tipping off defenders).